package utils;

import java.math.BigInteger;

/**
 * RSA_Password algoritam
 *
 * 1. Generisati dva velika prosta broje P i Q, pri cemu je P <> Q
 * 2. Izracunati: n = p * q
 * 3. Izracunati Ojlerovu f-ju: t = (p-1)*(q-1)
 * 4. Odabrati celobrojnu vrednost e, pri cemu je 1 < e < t
 * 5. Izracunati d, pri cemu je d <=> 1 (mod t)
 * 6. Public_key je par (n,e), Private_key je par (n,d).
 * 
 * Sifrovanje poruke
 * Neka je m (data) poruka koju treba sifrovati.
 * 1. Racuna se c = m^e (mod n) => coded
 * 
 * Desifrovanje poruke
 * 1. Racuna se m = c^d (mod n) => decoded
 * 
 * 
 * Napomena:
 * Ovako izabrani P i Q omogucavaju kodiranje do 15 ASCII znakova zakljucno sa znakom ~, cija
 * je ASCII vrednost 126 (Jedino je veci DEL znak, ali on nije printabilan, niti se moze uneti
 * sa tastature kao string.)
 * Iz ovoga sledi garancija da ce se string: ~~~~~~~~~~~~~~~ pravilno kodiradi i dekodirati,
 * jer je njegova byte reprezentacija string:
 * 126126126126126126126126126126126126126126126, a t je broj koji je veci od ovog broja.
 * 
 * Posto vrednosti ASCII karaktera mogu biti jednocifrene, dvocifrene ili trocifrene,
 * uzeto je da su uvek trocifrene, pa se na jednocifrene vrednosti dodaju dve vodece nule,
 * a na dvocifrene vrednosti se dodaje jedna vodeca nule. Ovim je omoguceno pravilo
 * desifrovanje ASCII znakova.
 * 
 * @author UrosKrkic & Radivoje Popovic
 */
public class RSA_Password {
		
	private BigInteger P = new BigInteger("98389242311231133117131");
	private BigInteger Q = new BigInteger("52390317371231317171931");
	
	private BigInteger P_1 = P.subtract(new BigInteger("1"));
	private BigInteger Q_1 = Q.subtract(new BigInteger("1"));

	private BigInteger n = P.multiply(Q);
	private BigInteger t = P_1.multiply(Q_1);
	private BigInteger e = new BigInteger("337191917115173171");
	private BigInteger d = e.modInverse(t);
	
	private BigInteger data;		// podaci koje treba sifrovati
	private BigInteger coded;		// sifrovani podaci
	private BigInteger decoded;		// desifrovani podaci

	
	/**
	 * Konstruktor.
	 * @author UrosKrkic & Radivoje Popovic
	 */
	public RSA_Password() {}
	

	
	/**
	 * Metoda vraca string koji predstavlja kodiran podatak.
	 * @param data - podaci koje treba sifrovati
	 * @return - kodiran podatak
	 * @author UrosKrkic & Radivoje Popovic
	 */
	public String codeData(String data) {
		this.data = new BigInteger(convertStringToNumberRepresentation(data), 10);
		
		coded = this.data.modPow(e, n);
		
		return coded.toString();
	}

	

	/**
	 * Metoda vrsi dekodiranje podatka.
	 * @param data - podaci koje treba desifrovati
	 * @return - desifrovani podatak
	 * @author UrosKrkic & Radivoje Popovic
	 */
	public String decodeData(String data) {
		// TODO proveriti zasto ponekad ne radi
		try {
			this.coded = new BigInteger(data);
		}
		catch (NumberFormatException nfe) {
			this.coded = new BigInteger("0");
		}
		
		decoded = coded.modPow(d, n);
		
		String decodedString = decoded.toString(10).toUpperCase();
		
		// dodavanje vodecih nula
		int modul = decodedString.length() % 3;
		
		if (modul == 1) {
			decodedString = "00" + decodedString;
		} else
		if (modul == 2) {
			decodedString = "0" + decodedString;
		}

		return convertNumberRepresentationToString(decodedString);
	}


	
	/**
	 * Metod konvertuje ulazni string u byte reprezentaciju tog stringa, uz
	 * dodavanje vodecih nula kod jednocifrenih i dvocifrenih byte vrednosti
	 * string karaktera.
	 * NAPOMENA:
	 * Ova byte preprezentacija je takodje string koji se sastoji od konkateniranih
	 * trocifrenih byte vrednosti.
	 * NPR. String abc ce se kodirati kao: 097098099
	 * @param data - ulazni string
	 * @return - byte reprezentacija stringa
	 * @author UrosKrkic & Radivoje Popovic
	 */
	private String convertStringToNumberRepresentation(String data) {
		
		byte[] niz = data.getBytes();
		
		StringBuffer buffer = new StringBuffer();
		
		for (int i = 0; i < niz.length; i++) {
			if (niz[i] < 10) {
				buffer.append("00" + Byte.toString(niz[i]));
			} else
			if (niz[i] >= 10 && niz[i] <= 99) {
				buffer.append("0" + Byte.toString(niz[i]));
			} else {
				buffer.append(Byte.toString(niz[i]));
			}
		}
		
		return  buffer.toString();
	}
	

	
	/**
	 * Metod konvertuje trocifrenu byte stringovnu reprezentaciju stringa u string.
	 * Posto je svaki byte u ulaznom stringu predstavljen sa tri cifre, potrebno
	 * je iz ulaznog stringa uzimati po tri karaktera i konverotovati ih u byte-ove.
	 * Na kraju, iz tako dobijenog byte niza kreirati String objekat.
	 * @param data - ulazna byte stringovna reprezentacija.
	 * @return - string dobijen iz niza byte-ova
	 * @author UrosKrkic & Radivoje Popovic
	 */
	private String convertNumberRepresentationToString(String data) {
		
		byte[] niz = new byte[data.length() / 3];
		
		for (int i = 0; i < niz.length; i++) {
			String temp = data.substring(i*3, i*3 + 3);
			niz[i] = Byte.parseByte(temp);
		}
		
		return new String(niz);
	}
	
}
